/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.felix.scr.impl.logger;

import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;

import org.apache.felix.scr.impl.logger.InternalLogger.Level;
import org.apache.felix.scr.impl.manager.ScrConfiguration;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.log.Logger;

/**
 * Implements a SCR based log manager. This class was needed to not change the
 * whole codebase. It looks very similar to the old logging but leverages the
 * {@link LogManager}. This class implements the existing behavior and the
 * {@link ExtLogManager} implements the extension behavior. The {@link #scr()}
 * method makes this distinction based on the configuration.
 */
public class ScrLogManager extends LogManager {

	private final Bundle			bundle;
	private final ScrConfiguration	config;

	/**
	 * Get a new log manager based on the configuration.
	 * 
	 * @param context
	 *                    the bundle context of the SCR bundle
	 * @param config
	 *                    the SCR configuration
	 * @return a proper ScrLogManager
	 */

	public static ScrLogger scr(BundleContext context, ScrConfiguration config) {
        ScrLogManager manager;
		if (config.isLogExtension())
            manager = new ExtLogManager(context, config);
		else
            manager = new ScrLogManager(context, config);
        manager.open();
        return manager.scr();
	}

	ScrLogManager(BundleContext context, ScrConfiguration config) {
		super(context);
		this.config = config;
		this.bundle = context.getBundle();
	}

	/**
	 * This logger is used for the main code of SCR. This will use the SCR
	 * bundle & the {@link Logger#ROOT_LOGGER_NAME}
	 * 
	 * @return an Scr Logger.
	 */
	public ScrLogger scr() {
		ScrLoggerFacade scrl = super.getLogger(bundle, Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
		scrl.setPrefix(getBundleIdentifier(bundle));
		return scrl;
	}

	/**
	 * This logger is used for the logging on a per bundle basis. This will use
	 * the target bundle & the {@link Logger#ROOT_LOGGER_NAME}
	 * 
	 * @param bundle
	 *                   the target bundle
	 * @return a logger suitable to log bundle entries
	 */
	public BundleLogger bundle(Bundle bundle) {
		ScrLoggerFacade logger = getLogger(bundle, Logger.ROOT_LOGGER_NAME, ScrLoggerFacade.class);
		logger.setPrefix(getBundleIdentifier(bundle));
		return logger;
	}

	/**
	 * This logger is used for the logging on a per bundle basis. This will use
	 * the target bundle & the implementation class as logger name.
	 * 
	 * @param bundle
	 *                   the target bundle
	 * @return a logger suitable to log bundle entries
	 */
	public ComponentLogger component(Bundle bundle, String implementationClass, String name) {

		// assert bundle != null;
		// assert bundle.getSymbolicName() != null : "scr requires recent
		// bundles";
		// assert implementationClass != null;
		// assert name != null;

		ScrLoggerFacade facade = getLogger(bundle, implementationClass, ScrLoggerFacade.class);
		facade.setComponentId(-1);
		return (ComponentLogger) facade;
	}

	class ScrLoggerFacade extends LoggerFacade implements InternalLogger, ScrLogger, BundleLogger, ComponentLogger {
		ScrLoggerFacade(LogDomain logDomain, String name) {
			super(logDomain, name);
		}

		@Override
		public void setComponentId(long id) {
			setPrefix(componentPrefix(this, id));
		}

		public boolean isLogEnabled(Level level) {

			// assert !closed.get();

			Object checkLogger = getLogger();
			if (checkLogger != null) {
			    Logger logger = (Logger) checkLogger;
				switch (level) {
				case AUDIT:
					return true;
				case ERROR:
					return logger.isErrorEnabled();
				case WARN:
					return logger.isWarnEnabled();
				case INFO:
					return logger.isInfoEnabled();
				case TRACE:
					return logger.isTraceEnabled();
				case DEBUG:
				default:
					return logger.isDebugEnabled();
				}
			} else {
				return getLogLevel().implies(level);
			}
		}

		@Override
		public void log(Level level, String format, Throwable ex, Object... arguments) {
			if (isLogEnabled(level))
				log0(level, format(format, arguments), ex);
		}

		@Override
		public void log(Level level, String message, Throwable ex) {
			if (isLogEnabled(level))
				log0(level, message, ex);
		}

		void log0(Level level, String message, Throwable ex) {
			if (prefix != null && prefix.length() > 0) {
				message = prefix.concat(" ").concat(message);
			}
			Object checkLogger = getLogger();
			if (checkLogger != null) {
			    Logger logger = (Logger) checkLogger;
				if (ex == null) {
					switch (level) {
					case AUDIT:
						logger.audit(message);
						break;
					case ERROR:
						logger.error(message);
						break;
					case WARN:
						logger.warn(message);
						break;
					case INFO:
						logger.info(message);
						break;
					case TRACE:
						logger.trace(message);
						break;
					case DEBUG:
					default:
						logger.debug(message);
					}
				} else {
					switch (level) {
					case AUDIT:
						logger.audit(message, ex);
						break;
					case ERROR:
						logger.error(message, ex);
						break;
					case WARN:
						logger.warn(message, ex);
						break;
					case INFO:
						logger.info(message, ex);
						break;
					case TRACE:
						logger.trace(message, ex);
						break;
					case DEBUG:
					default:
						logger.debug(message, ex);
					}
				}
			} else {
				StringWriter buf = new StringWriter();
				String l = String.format("%-5s", level);
				buf.append(l).append(" : ").append(message);
				if (ex != null) {
					try (PrintWriter pw = new PrintWriter(buf)) {
						pw.println();
						ex.printStackTrace(pw);
					}
				}

				@SuppressWarnings("resource")
				PrintStream out = level.err() ? System.err : System.out;
				out.println(buf);
			}
		}

		void setPrefix(String prefix) {
			this.prefix = prefix;
		}

		@Override
		public ComponentLogger component(Bundle bundle, String implementationClassName, String name) {
			// assert !closed.get();
			return ScrLogManager.this.component(bundle, implementationClassName, name);
		}

		@Override
		public BundleLogger bundle(Bundle bundle) {
			// assert !closed.get();
			return ScrLogManager.this.bundle(bundle);
		}

		@Override
		public void close() {
			// assert !closed.get();
			ScrLogManager.this.close();
		}
	};

	LoggerFacade createLoggerFacade(LogDomain logDomain, String name) {
		// assert !closed.get();
		return new ScrLoggerFacade(logDomain, name);
	}

	Level getLogLevel() {
		return config.getLogLevel();
	}

	String getBundleIdentifier(final Bundle bundle) {
		final StringBuilder sb = new StringBuilder("bundle ");

		if (bundle.getSymbolicName() != null) {
			sb.append(bundle.getSymbolicName());
			sb.append(':');
			sb.append(bundle.getVersion());
			sb.append(" (");
			sb.append(bundle.getBundleId());
			sb.append(")");
		} else {
			sb.append(bundle.getBundleId());
		}

		return sb.toString();
	}

	String componentPrefix(ScrLoggerFacade slf, long id) {
		if (id >= 0) {
			return getBundleIdentifier(slf.getBundle()) + "[" + slf.getName() + "(" + id + ")] :";
		} else {
			return getBundleIdentifier(slf.getBundle()) + "[" + slf.getName() + "] :";
		}
	}

	String format(final String pattern, final Object... arguments) {
		if (arguments == null || arguments.length == 0) {
			return pattern;
		} else {
			for (int i = 0; i < arguments.length; i++) {
				if (arguments[i] instanceof Bundle) {
					arguments[i] = getBundleIdentifier((Bundle) arguments[i]);
				}
			}
			return MessageFormat.format(pattern, arguments);
		}
	}

}
